/*
 * @Author: zhang_gen_yuan
 * @Date: 2020-12-17 20:06:03
 * @LastEditTime: 2020-12-21 10:36:39
 * @Descripttion: 
 */

// 处理各种指令  v-text v-html 
const compileDir = {
    getVal(expr, vm) {
        return expr.split('.').reduce((data, currentVal) => {
            let cc = currentVal.trim();
            return data[cc]
        }, vm.$data)
    },
    text(node, expr, vm) {
        let val;
        if (expr.indexOf('{{') !== -1) {
            val = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                // 添加观察者
                new Watcher(vm, args[1], () => {
                    //更新视图
                    this.textUpdater(node, this.getContentVal(expr, vm))
                })
                return this.getVal(args[1].trim(), vm);
            })
        } else {
            val = this.getVal(expr, vm);
        }
        this.textUpdater(node, val);
    },
    textUpdater(node, value) {
        node.textContent = value;
    },
    getContentVal(expr, vm) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(args[1], vm);
        })
    },
}

class MyVue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if (this.$el) {
            new Observer(this.$data);
            this.proxyData(this.$data);
            new Compile(this.$el, this);
        }
    }
    //外层糊上一层代理
    proxyData(data) {
        for (const key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                },
                set(newVal) {
                    data[key] = newVal;
                }
            })
        }
    }
}

/*
*  模板编译思路 
*    传入两个参数  一个el #app   唯一父元素   还有一个 vm 其实就是vue   new Vue()
*    1.创建文档碎片   (提升性能) nodeFragment方法递归  appendChild(firstChild)
*    2.编译模板 
*         2.1 拿到文档碎片  通过childNodes  数据forEach遍历 拿到child 通过nodeType===1 判断拿到的child是一个元素节点还是文本节点
*             然后执行两种不同的操作,然后通过child.childNodes判断child下是否有子元素 如果有--递归
*         2.2 元素节点:通过 attributes 拿到属性数组  对数组进行遍历 结构拿到 { name,value } name指的是具体的指令名,value指的是指令对应的值
*             通过es6判断startsWith('v-')  如果是指令名  通过'-'进行切割,拿到后面具体值,声明一个对象  里面存储针对不同指令执行不同操作(html,text,once)
*         2.3 文本节点 : node.textContent拿到文本值   正则匹配双花括号  替换成具体值
*      !!!!!!!!!处理值的时候添加观察者
*    3.碎片追加到唯一父元素下
*
*
*/

//模版编译
class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        //1.创建文档碎片
        const frag = this.nodeFragment(this.el);
        //2.编译模版
        this.compileTemplate(frag);
        //3.添加到父节点中
        this.el.appendChild(frag);
    }
    //判断当前是不是元素节点
    isElementNode(node) {
        return node.nodeType === 1;
    }
    //创建文档碎片
    nodeFragment(node) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = node.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment;
    }
    //编译模版
    compileTemplate(frag) {
        //获取子节点  对数组进行遍历  判断是否元素节点和文本节点  
        let childNodes = frag.childNodes;
        [...childNodes].forEach(child => {
            if (this.isElementNode(child)) {
                //  执行元素节点处理方法
                this.elementNode(child)
            } else {
                //  执行文本节点处理方法
                this.textNode(child)
            }
            if (child.childNodes && child.childNodes.length) {
                // 递归执行编译模板
                this.compileTemplate(child)
            }
        })
    }

    //如果是元素节点
    elementNode(node) {
        const attributes = node.attributes;
        [...attributes].forEach(attr => {
            const { name, value } = attr;
            if (this.isDirective) {
                let [, dirName] = name.split('-');
                compileDir.text(node, value, this.vm);
                node.removeAttribute('v-'+dirName)
            }
        })
    }
    //如果是文本节点
    textNode(node) {
        const content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            compileDir['text'](node, content, this.vm)
        }
    }
    //判断是不是以v-开头的指令
    isDirective(name) {
        return name.startsWith('v-')
    }
}
